package com.cvtt.xmpp;

import java.io.File;
import java.util.Iterator;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.packet.DeliveryReceipt;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import android.os.RemoteException;
import android.util.Log;

import com.cvtt.xmpp.pep.PepSubManager;
import com.cvtt.xmpp.ping.PingExtension;
import com.cvtt.xmpp.utils.Status;

public class XMPPConnectionAdapter {

	private static final int SMACK_PRIORITY_MIN = -128;
	private static final int SMACK_PRIORITY_MAX = 128;
	private static final String LOGTAG = "XMPPConnectionAdapter";
	private XMPPConnection xmppConnection;
	private int nPreviousPriority;
	private int nPreviousMode;
	private String strPreviousStatus;
	private PepSubManager pepManager;
	private ConnectionListener connListener;
	private ConnectionConfiguration connConfig;

	private XMPPEngine xmppEngine;

	public XMPPConnectionAdapter(XMPPEngine xmppEngine) {
		this.xmppEngine = xmppEngine;
		initConnectionConfig();
		xmppConnection = new XMPPConnection(connConfig);
	}

	private ConnectionConfiguration initConnectionConfig() {
		// TODO Auto-generated method stub
		connConfig = new ConnectionConfiguration(XMPPConfig.XMPP_HOST, XMPPConfig.XMPP_PORT);

		if (XMPPConfig.useSecurityMode)
			connConfig.setSecurityMode(SecurityMode.required);
		else
			connConfig.setSecurityMode(SecurityMode.disabled);

		if (XMPPConfig.LOG_ENABLE)
			connConfig.setDebuggerEnabled(true);
		else
			connConfig.setDebuggerEnabled(false);

		connConfig.setTruststoreType("BKS");
		String path = System.getProperty("javax.net.ssl.trustStore");
		if (path == null) {
			path = System.getProperty("java.home") + File.separator + "etc" + File.separator + "security" + File.separator + "cacerts.bks";
		}
		connConfig.setTruststorePath(path);
		if (XMPPConfig.useCompression)
			connConfig.setCompressionEnabled(true);
		else
			connConfig.setCompressionEnabled(false);
		connConfig.setReconnectionAllowed(false);
		connConfig.setSendPresence(false);

		return connConfig;
	}

	public XMPPConnection getXMPPConnection() {
		return xmppConnection;
	}

	public XMPPConnection getValidXMPPConnection() {
		if (!xmppConnection.isAuthenticated())
			return null;
		if (!xmppConnection.isConnected())
			return null;
		return xmppConnection;
	}

	/**
	 * 是否已连接
	 * */
	public boolean isConnected() {
		if (xmppConnection == null)
			return false;
		return xmppConnection.isConnected();
	}

	/**
	 * 连接是否有效
	 * */
	public boolean isConnectionValid() {
		if (xmppConnection == null)
			return false;
		if (!xmppConnection.isAuthenticated())
			return false;
		if (!xmppConnection.isConnected())
			return false;
		return true;
	}

	public void connect() throws RemoteException, XMPPException {
		if (xmppConnection.isConnected())
			return;
		else
			xmppConnection.connect();
	}

	// Modified by huah in 2012-11-29
	public boolean login(String username, String passwd) throws RemoteException, XMPPException {
		if (xmppConnection == null) {
			return false;
		}

		if (!xmppConnection.isConnected()) {
			return false;
		}
		if (xmppConnection.isAuthenticated()) {
			return true;
		}

		this.initFeatures(); // pour declarer les features xmpp qu'on
		xmppConnection.login(username, passwd, XMPPConfig.XMPP_RESOURCE_NAME);
		if (!xmppConnection.isAuthenticated()) {
			return false;
		}
		connListener = new ConnectionListenerAdapter();
		xmppConnection.addConnectionListener(connListener);
		discoverServerFeatures();

		return true;
	}

	public boolean login() throws RemoteException, XMPPException {
		return login(XMPPConfig.XMPP_USERNAME, XMPPConfig.XMPP_PASSWORD);
	}

	/**
	 * {@inheritDoc}
	 */
	public void changeStatusAndPriority(int status, String msg, int priority) {
		Presence pres = new Presence(Presence.Type.available);
		String m;
		if (msg != null)
			m = msg;
		else
			m = strPreviousStatus;
		pres.setStatus(m);
		strPreviousStatus = m;
		Presence.Mode mode = Status.getPresenceModeFromStatus(status);
		if (mode != null) {
			pres.setMode(mode);
			nPreviousMode = status;
		}
		else {
			pres.setMode(Status.getPresenceModeFromStatus(nPreviousMode));
		}
		int p = priority;
		if (priority < SMACK_PRIORITY_MIN)
			p = SMACK_PRIORITY_MIN;
		if (priority > SMACK_PRIORITY_MAX)
			p = SMACK_PRIORITY_MAX;
		nPreviousPriority = p;
		pres.setPriority(p);
		xmppConnection.sendPacket(pres);
	}

	/**
	 * {@inheritDoc}
	 */
	public void changeStatus(int status, String msg) {
		changeStatusAndPriority(status, msg, nPreviousPriority);
	}

	/**
	 * get the previous status.
	 * 
	 * @return previous status.
	 */
	public String getPreviousStatus() {
		return strPreviousStatus;
	}

	/**
	 * get the previous mode.
	 * 
	 * @return previous mode.
	 */
	public int getPreviousMode() {
		return nPreviousMode;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean disconnect() {
		if (xmppConnection != null && xmppConnection.isConnected()) {
			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "before xmppConnection.disconnect();");

			xmppConnection.disconnect();
			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "after xmppConnection.disconnect();");
		}
		return true;
	}

	public boolean disconnectSync() {
		if (xmppConnection != null && xmppConnection.isConnected()) {
			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "before xmppConnection.disconnect();");

			xmppConnection.disconnect();

			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "after xmppConnection.disconnect();");
		}
		return true;
	}

	/**
	 * Returns true if currently authenticated by successfully calling the login method.
	 * 
	 * @return true when successfully authenticated
	 */
	public boolean isAuthentificated() {
		if (xmppConnection == null)
			return false;
		return xmppConnection.isAuthenticated();
	}

	/**
	 * Initialize the features provided by beem.
	 */
	private void initFeatures() {
		ServiceDiscoveryManager.setIdentityName("Toc");
		ServiceDiscoveryManager.setIdentityType("phone");
		ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(xmppConnection);
		if (sdm == null)
			sdm = new ServiceDiscoveryManager(xmppConnection);

		sdm.addFeature("http://jabber.org/protocol/disco#info");
		// nikita: must be uncommented when the feature will be enabled
		// sdm.addFeature("jabber:iq:privacy");
		sdm.addFeature("http://jabber.org/protocol/caps");
		sdm.addFeature("urn:xmpp:avatar:metadata");
		sdm.addFeature("urn:xmpp:avatar:metadata+notify");
		sdm.addFeature("urn:xmpp:avatar:data");
		sdm.addFeature("http://jabber.org/protocol/nick");
		sdm.addFeature("http://jabber.org/protocol/nick+notify");
		sdm.addFeature(PingExtension.NAMESPACE);
		sdm.addFeature(DeliveryReceipt.NAMESPACE);

		// Connection connection = xmppConnection;
		// CapsManager caps = new CapsManager(sdm, xmppConnection);
		// caps.setNode("http://www.toc-project.com");
	}

	/**
	 * Discover the features provided by the server.
	 */
	private void discoverServerFeatures() {
		try {
			// jid et server
			ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(xmppConnection);
			DiscoverInfo info = sdm.discoverInfo(xmppConnection.getServiceName());
			Iterator<DiscoverInfo.Identity> it = info.getIdentities();
			while (it.hasNext()) {
				DiscoverInfo.Identity identity = it.next();
				if ("pubsub".equals(identity.getCategory()) && "pep".equals(identity.getType())) {
					initPEP();
				}
			}
		}
		catch (XMPPException e) {
			Log.w(LOGTAG, "Unable to discover server features", e);
		}
	}

	/**
	 * Initialize PEP.
	 */
	private void initPEP() {
		// Enable pep sending
		if (XMPPConfig.LOG_ENABLE)
			Log.d(LOGTAG, "Pep enabled");
		// API 8
		// mService.getExternalCacheDir()
		pepManager = new PepSubManager(xmppConnection);

	}

	/**
	 * Listener for XMPP connection events. It will calls the remote listeners for connection events.
	 */
	private class ConnectionListenerAdapter implements ConnectionListener {

		/**
		 * Defaut constructor.
		 */
		public ConnectionListenerAdapter() {
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void connectionClosed() {
			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "closing connection");

		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void connectionClosedOnError(Exception exception) {
			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "connectionClosedOnError" + exception.getLocalizedMessage());

			if (exception.getMessage().contains("Replaced by new connection")) {
				xmppEngine.onConnectionReplaced();
				Log.d(LOGTAG, "connectionClosedOnError" + exception.getLocalizedMessage());
			}

		}

		/**
		 * Connection failed callback.
		 * 
		 * @param errorMsg
		 *            smack failure message
		 */
		public void connectionFailed(String errorMsg) {
			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "Connection Failed");

			// resetApplication();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void reconnectingIn(int arg0) {
			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "reconnectingIn");

		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void reconnectionFailed(Exception arg0) {
			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "reconnectionFailed");

		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void reconnectionSuccessful() {
			if (XMPPConfig.LOG_ENABLE)
				Log.d(LOGTAG, "reconnectionSuccessful");
		}
	}
}